<?php

namespace api\components\dataCenter;

use api\utils\LogUtil;
use Exception;
use Yii;

class SearchEngine
{
    use QueryTrait;

    const COMMON_SEARCH_URL = '/united-ciimc-api/v1/generic-query';

    const INDEX_TYPE_ACTIVE = 'active';
    const INDEX_TYPE_HISTORY = 'history';

    const SORT_ASC = 'asc';
    const SORT_DESC = 'desc';

    private $table;

    /**
     * @var DataCenterSearchModel|null
     */
    private $model_class;

    /**
     * 待查询字段
     * @var array
     */
    private $select = [];

    /**
     * 查询条数 最大10000条
     * 若没有任何限制条件 默认取最大数量
     * @var int
     */
    private $limit = 10000;

    /**
     * 查询偏移量
     * @var int
     */
    private $offset = 0;

    /**
     * @var string
     */
    private $index_type = self::INDEX_TYPE_ACTIVE;

    /**
     * 排序
     * @var string[]
     */
    private $sort = [];

    /**
     * @var SearchFilterBuilder
     */
    private $filter_builder;

    private $new_filter;

    private $search_host;

    private $search_token;

    private $asArray = true;

    /**
     * 最后一次查询使用的参数
     * @var array
     */
    private $last_search_params = [];

    /**
     * SearchFilterBuilder constructor.
     * @param string|null $table 需要查询数据中台的表名
     */
    public function __construct(?string $table = null)
    {
        $this->table = $table;
        $this->filter_builder = new SearchFilterBuilder();
        $this->search_host = DATA_CENTER_HOST;
        $this->search_token = DATA_CENTER_TOKEN;
    }

    public function setHost($host): self
    {
        $this->search_host = $host;
        return $this;
    }

    public function setToken($token): self
    {
        $this->search_token = $token;
        return $this;
    }

    public function modelClass($model): self
    {
        $this->model_class = $model;
        return $this;
    }

    /**
     * 设置index_type
     * @param string $index_type 数据中台数据的index_type
     * @return $this
     */
    public function indexType(string $index_type): self
    {
        $this->index_type = $index_type;
        return $this;
    }

    /**
     * 需要返回数组还是对象（仅当设置了model_class时可用）
     * @param bool $asArray
     * @return $this
     */
    public function asArray($asArray = true): self
    {
        $this->asArray = $asArray;
        return $this;
    }

    /**
     * 新增and查询条件
     * ['field1'=>'value1' , 'field2=>'value2' , 'field3=>'value3'] or
     * [ 'operator' , 'field' , 'value' , 'data_type' ] or
     * [ 'and' | 'or' , ['field1'=>'value1'] , ['field2'=>'value2'] ] or
     * [ 'and' | 'or' , [ 'operator' , 'field' , 'value' , 'data_type' ] , [ 'operator' , 'field' , 'value' , 'data_type' ] ]
     * example:
     *  $query->andWhere(['a'=>'1']); => where a = 1
     *  $query->andWhere(['>','a','1']); => where a > 1
     *  $query->andWhere(['or' , ['a'=>'1'] , ['b'=>'2']]); => where `otherwhere` and ( a=1 or b=2 )
     *  $query->andWhere(['and' , ['>','a','1'] , ['<','b','2']]); => where `otherwhere` and ( a>1 and b<2 )
     * @param array $condition
     * @return SearchEngine
     */
    public function andWhere(array $condition): self
    {
        $this->filter_builder->andWhere($condition);
        return $this;
    }

    /**
     * 新增or查询条件
     * 具体规则同 and 条件
     * @param array $condition
     * @return $this
     */
    public function orWhere(array $condition): self
    {
        $this->filter_builder->orWhere($condition);
        return $this;
    }

    /**
     * 新增 and raw 查询条件
     * 直接输入数据中台需要的filter表达式
     * @param string $condition
     * @return $this
     */
    public function andRawWhere(string $condition): self
    {
        $this->filter_builder->andRawWhere($condition);
        return $this;
    }

    /**
     * 新增 and filter 查询条件
     * 具体规则同 and 条件
     * 会自动判断取值是否为 null 或 '' 或 []
     * 取值有效时才会生效 否则无效
     * @param array $condition
     * @return $this
     */
    public function andFilterWhere(array $condition): self
    {
        $this->filter_builder->andFilterWhere($condition);
        return $this;
    }

    /**
     * 新增 or filter 查询条件
     * 具体规则同 and 条件
     * 会自动判断取值是否为 null 或 '' 或 []
     * 取值有效时才会生效 否则无效
     * @param array $condition
     * @return $this
     */
    public function orFilterWhere(array $condition): self
    {
        $this->filter_builder->orFilterWhere($condition);
        return $this;
    }

//    /**
//     * 设置查询字段
//     * @param array $select
//     * @return $this
//     */
//    public function select(array $select): self
//    {
//        $this->select = $select;
//        return $this;
//    }

    /**
     * 新增查询字段
     * @param array|string $select
     * @return SearchEngine
     */
    public function addSelect($select): self
    {
        if (empty($select)) return $this;
        $this->select = array_unique(
            array_merge(
                $this->select,
                is_array($select) ? $select : explode(',', $select)
            )
        );
        return $this;
    }

    public function limit($limit): self
    {
        $this->limit = intval($limit);
        return $this;
    }

    public function offset($offset): self
    {
        $this->offset = intval($offset);
        return $this;
    }

    public function page($page, $pageSize): self
    {
        $this->limit = intval($pageSize);
        $this->offset = intval($pageSize) * (intval($page) - 1);
        return $this;
    }

    public function sort(string $sort_by, ?string $sort_type = self::SORT_ASC): self
    {
        $this->sort[] = $sort_type ? "$sort_by|$sort_type" : $sort_by;
        return $this;
    }

    /**
     * 获取最后一次查询使用的参数
     * @return array
     * @throws Exception
     */
    public function getParams()
    {
        return $this->last_search_params;
    }

    /**
     * 查询结果条数
     * @param array $condition
     * @param array $other_params
     * @return int
     * @throws Exception
     */
    public function count(array $condition = [], array $other_params = []): int
    {
        !empty($condition) && $this->andWhere($condition);
        $other_params['limit'] = 0;
        $result = $this->getResult($other_params);
        return intval($result['data']['numHits']);
    }

    /**
     * 查询所有结果
     * @param array $condition 查询条件
     * @param bool $withCount 是否返回count条数 $withCount 为 true 时，返回 [ $data , $count ] , 否则返回 $data
     * @param array $other_params 附加参数
     * @return array|DataCenterSearchModel[]
     * @throws Exception
     */
    public function findAll(array $condition = [], bool $withCount = false, array $other_params = []): array
    {
        !empty($condition) && $this->andWhere($condition);
        $result = $this->getResult($other_params);
        $data = $result['data']['messages'];
        $count = $result['data']['numHits'];
        if ($this->asArray || !$this->model_class) {
            return $withCount ? [$data, $count] : $data;
        } else {
            $model = new $this->model_class(false);
            $data = array_map(
                function ($value) use ($model) {
                    return (clone $model)->fill($value);
                },
                $data
            );
            return $withCount ? [$data, $count] : $data;
        }
    }

    /**
     * 按组查询
     * 注意 无法进行超大组查询 每组最大只能存在100条数据
     * @param string $group_by 需要分组的字段
     * @param string $group_sort_field 分组内排序字段
     * @param string $sort 分组内排序类型
     * @param bool $withCount 是否返回count条数 $withCount 为 true 时，返回 [ $data , $count ] , 否则返回 $data
     * @return array
     * @throws Exception
     */
    public function findGroup(string $group_by, string $group_sort_field, string $sort = self::SORT_ASC, $withCount = false)
    {
        $other_params = [
            'limit' => 0,
            'offset' => 0,
            'group_limit' => $this->limit,
            'group_offset' => $this->offset,
            'group_by' => "$group_by|top_hits.100.$group_sort_field.$sort",
            'transform' => 'aggResults.*[]'
        ];
        $result = $this->getResult($other_params);
        //若无数据，也会返回一个空组，所以numHits为1，但此时total应该为0，单独处理
        if ($result['numHits'] == 1 && $result['data'][0]['count'] == 0) {
            return $withCount ? [[], 0] : [];
        }
        $data = array_map(
            function ($value) use ($group_by) {
                return [
                    'count' => $value['count'],
                    'group' => $value['topN'][0]['data'][$group_by],
                    'data' => $value['topN']
                ];
            },
            $result['data']
        );
        return $withCount ? [$data, $result['numHits']] : $data;
    }

    /**
     * transform查询(常用于一些数据统计)
     * @param string $group_by
     * @param string $transform
     * @param string|null $original
     * @return array
     * @throws Exception
     */
    public function findTransform(string $group_by, string $transform, ?string $original = null)
    {
        $other_params = [
            'limit' => 0,
            'offset' => 0,
            'group_by' => $group_by,
            'transform' => $transform
        ];
        isset($original) && $other_params['original'] = $original;
        return $this->getResult($other_params);
    }

    /**
     * 查询单个结果
     * @param array $condition
     * @param array $other_params
     * @return array|null|DataCenterSearchModel
     * @throws Exception
     */
    public function findOne(array $condition = [], array $other_params = [])
    {
        !empty($condition) && $this->andWhere($condition);
        $other_params['limit'] = 1;
        $result = $this->getResult($other_params)['data']['messages'];
        if (empty($result)) {
            return null;
        } else {
            return $this->asArray || !$this->model_class
                ? $result[0] : (new $this->model_class(false))->fill($result[0]);
        }
    }

    /**
     * 导出数据
     * @param array $export_field
     * demo : [
     *      // field:字段名
     *      // title:标题名
     *      // default:若不存在此字段取的默认值，没有此值默认为空字符串
     *      // map:字典
     *      // formatter:传入一个格式化方法
     *
     *      [ 'field' => 'test1', 'title' => 'test1' , 'default' => 'test' ,'map'=> [1,2,3,4] ],
     *      [ 'field' => 'test2', 'title' => 'test2' , 'formatter' => function($value){ return $value; } ],
     * ]
     * @param string|int $uid
     * @param array $condition
     * @param int|null $offline_download_min_num
     * @return array
     * @throws Exception
     */
    public function export(array $export_field, $uid, array $condition = [], ?int $offline_download_min_num = null)
    {
        if (empty($export_field)) {
            throw new Exception('未选择任何导出字段');
        }

        if (!empty($condition)) {
            $this->andWhere($condition);
        }
        $export = new ExportToCsv(clone $this);
        $offline_download_min_num && $export->setOfflineDownloadMinNum($offline_download_min_num);

        return $export->export($export_field, $uid)->getResult();
    }

    public function setNewFilter($newFilter)
    {
        $this->new_filter = $newFilter;
    }

    /**
     * @param array $other_params
     * @return array
     * @throws Exception
     */
    private function getResult(array $other_params): array
    {
        // $filter = $this->filter_builder->toFilterText();
        if (empty($this->new_filter)) {
            $filter = $this->filter_builder->toFilterObj();
        } else {
            $filter = $this->new_filter;
        }
        $params = [
            'index_type' => $this->index_type,
            'table' => $this->table,
            'limit' => $this->limit,
            'offset' => $this->offset,
            'token' => $this->search_token
        ];
        !empty($this->sort) && $params['sort_by'] = implode(',', $this->sort);
        !empty($filter) && $params['new_filter'] = json_encode($filter, JSON_UNESCAPED_UNICODE);
        if (!empty($this->select) && !in_array('*', $this->select)) {
            $params['source'] = implode(',', $this->select);
        }
        $url = self::COMMON_SEARCH_URL;
        $result = $this->post($params, $url);
        if (empty($result['result']) || $result['result'] !== 'success') {
            LogUtil::warn([
                'message' => '数据中台查询返回错误',
                'response' => $result,
                'url' => $url
            ], 'data-center');
            throw new Exception('数据中台返回错误 ' . ($result['content'] ?? ''));
        }
        $this->last_search_params = $params;

        return $result;
    }

    public function get(array $params, string $url = self::COMMON_SEARCH_URL)
    {
        if (!isset($params['token'])) {
            $params['token'] = $this->search_token;
        }
        !empty($params) && $url = $url . '?' . http_build_query($params);

        return Yii::$app->curl->get($url);
    }

    public function post(array $params, string $url = self::COMMON_SEARCH_URL)
    {
        if (!isset($params['token'])) {
            $params['token'] = $this->search_token;
        }
        $url = $this->search_host . $url;

        return Yii::$app->curl->post($url, $params, [], false);
    }

}
